package core;

import static org.junit.Assert.*;

import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import core.exception.IllegalAddException;
import core.exception.NotFoundException;

public class ProductManagerTest 
{
	private ProductManager pManager;
	private ItemManager iManager;
	private Product product1;
	private Product product2;
	private Product product3;
	private Product product4;
	private Container rootSU;
	private Container container1;
	private Container container2;
	private Container container3;
	private Barcode barcode1;
	private Barcode barcode2;
	private Barcode barcode3;
	private Barcode barcode4;
	
	@Before
	public void setUp() throws Exception 
	{
		ProductManager.setInstance(null);
		ItemManager.setInstance(null);
		pManager = ProductManager.instance();
		iManager = ItemManager.instance();
		barcode1 = new Barcode("023456789012", BarcodeType.PRODUCT);
		barcode2 = new Barcode("923456789012", BarcodeType.PRODUCT);
		barcode3 = new Barcode("323456789012", BarcodeType.PRODUCT);
		barcode4 = new Barcode("555566667777", BarcodeType.PRODUCT);
		Amount amt = new Amount(7, UnitOfMeasurement.grams);
		product1 = new Product(barcode1, "jump", amt, 13, 0);
		product2 = new Product(barcode2, "desc", amt, 2, 15);
		product3 = new Product(barcode3, "crazy", amt, 0, 75);
		product4 = new Product(barcode4, "dfs", amt, 10, 20);
		rootSU = RootStorageUnit.instance();
		container1 = rootSU.addContainer("first one");
		container2 = rootSU.addContainer("second one");
		container3 = rootSU.addContainer("third one");
	}

	@After
	public void tearDown() throws Exception 
	{
		RootStorageUnit.setInstance(null);
		pManager = null;
		barcode1 = null;
		barcode2 = null;
		barcode3 = null;
		product1 = null;
		product2 = null;
		product3 = null;
		rootSU = null;
		container1 = null;
		container2 = null;
		container3 = null;
	}

	@Test
	public void testInstance() 
	{
		assertEquals(pManager, ProductManager.instance());
		assertEquals(pManager, ProductManager.instance());
	}

	@Test
	public void testSetInstance() 
	{
		assertSame(pManager, ProductManager.instance());
		pManager.setInstance(null);
		assertNotSame(pManager, ProductManager.instance());
		assertNotNull(ProductManager.instance());
	}

	@Test
	public void testAddAndContainsProduct() 
	{
		assertFalse(pManager.containsProduct(barcode1));
		assertFalse(pManager.containsProduct(barcode2));
		pManager.addProduct(product1);
		assertTrue(pManager.containsProduct(barcode1));
		assertFalse(pManager.containsProduct(barcode2));
		pManager.addProduct(product2);
		assertTrue(pManager.containsProduct(barcode2));
	}

	@Test
	public void testCanAddProduct() 
	{
		assertFalse(pManager.canAddProduct(null));
		assertTrue(pManager.canAddProduct(product1));
		pManager.addProduct(product1);
		assertFalse(pManager.canAddProduct(product1));
	}

	@Test
	public void testGetProduct() 
	{
		assertNull(pManager.getProduct(barcode1));
		addThreeProducts();
		assertEquals(product1, pManager.getProduct(barcode1));
		assertEquals(product3, pManager.getProduct(barcode3));
		assertEquals(product2, pManager.getProduct(barcode2));
	}

	@Test
	public void testGetContainersForProduct() throws NotFoundException
	{
		addProductsAndContainers();
		Set<Container> set = getContainersForProduct1();
		Iterator<Container> iterator = pManager.getContainersForProduct(product1);
		testIteratorToSet(iterator, set);
		iterator = pManager.getContainersForProduct(product1);
		testIteratorToSet(iterator, set);
		
		set = getContainersForProduct2();
		iterator = pManager.getContainersForProduct(product2);
		testIteratorToSet(iterator, set);
		iterator = pManager.getContainersForProduct(product2);
		testIteratorToSet(iterator, set);
		
		set = getContainersForProduct3();
		iterator = pManager.getContainersForProduct(product3);
		testIteratorToSet(iterator, set);
		iterator = pManager.getContainersForProduct(product3);
		testIteratorToSet(iterator, set);
	}
	
	@Test (expected = NotFoundException.class)
	public void testGetContainersForProductException1() throws NotFoundException
	{
		Product product = null;
		pManager.getContainersForProduct(product);
	}
	
	@Test (expected = NotFoundException.class)
	public void testGetContainersForProductException2() throws NotFoundException
	{
		Product product = null;
		pManager.getContainersForProduct(product);
	}
	
	@Test (expected = NotFoundException.class)
	public void testGetContainersForProductException3() throws NotFoundException
	{
		pManager.getContainersForProduct(product4);
	}

	//test that iterator to equivalent set
	@SuppressWarnings("rawtypes")
	private void testIteratorToSet(Iterator iterator, Set set)
	{
		int count = 0;
		boolean everythingInSet = true;
		while (iterator.hasNext())
		{
			count++;
			Object obj = iterator.next();
			if (!set.contains(obj))
			{
				everythingInSet = false;
				break;
			}
		}
		assertTrue(everythingInSet);
		assertEquals(set.size(), count);
	}
	
	@Test
	public void testGetProductsForContainer() throws NotFoundException
	{
		addProductsAndContainers();
		Set<Product> set = getProductsForContainer1();
		Iterator<Product> iterator = pManager.getProductsForContainer(container1);
		testIteratorToSet(iterator, set);
		
		set = getProductsForContainer2();
		iterator = pManager.getProductsForContainer(container2);
		testIteratorToSet(iterator, set);
	}
	
	@Test (expected = NotFoundException.class)
	public void testGetProductsForContainerException1() throws NotFoundException
	{
		Container container = null;
		pManager.getProductsForContainer(container);
	}
	
	@Test (expected = NotFoundException.class)
	public void testGetProductsForContainerException2() throws NotFoundException
	{
		pManager.getProductsForContainer(container1);
	}

	@Test
	public void testProductContainsContainer() throws IllegalAddException 
	{
		addProductsAndContainers();
		assertFalse(pManager.productContainsContainer(product1, container1));
		assertFalse(pManager.productContainsContainer(product1, container2));
		assertFalse(pManager.productContainsContainer(product1, container3));
		assertTrue(pManager.productContainsContainer(product2, container1));
		assertTrue(pManager.productContainsContainer(product2, container2));
		assertTrue(pManager.productContainsContainer(product2, container3));
		assertFalse(pManager.productContainsContainer(product3, container1));
		assertTrue(pManager.productContainsContainer(product3, container2));
		assertFalse(pManager.productContainsContainer(product3, container3));
		assertFalse(pManager.productContainsContainer(product4, container1));
		assertFalse(pManager.productContainsContainer(null, container1));
		assertFalse(pManager.productContainsContainer(product2, null));
	}

	@Test
	public void testCanAddProductToContainer() 
	{
		assertFalse(pManager.canAddProductToContainer(product1, container1));
		
		addProductsAndContainers();
		//test null
		Product product = null;
		assertFalse(pManager.canAddProductToContainer(product, container2));
		assertFalse(pManager.canAddProductToContainer(product1, null));
		
		//test not in system
		assertFalse(pManager.canAddProductToContainer(product4, container2));
		
		//test already linked
		assertFalse(pManager.canAddProductToContainer(product3, container2));
		
		//test valid & doesn't change with repeated calls
		assertTrue(pManager.canAddProductToContainer(product3, container1));
		assertTrue(pManager.canAddProductToContainer(product3, container1));
	}


	@Test
	public void testRemoveProduct() 
	{
		//TODO: add testing that items for product have been removed
		try
		{
			addProductsAndContainers();
			Set<Container> set = getContainersForProduct2();
			Iterator<Container> contIterator = pManager.getContainersForProduct(product2);
			testIteratorToSet(contIterator, set);
			assertTrue(pManager.containsProduct(barcode2));
			
			pManager.removeProduct(product2);
			
			assertFalse(pManager.containsProduct(barcode2));
			
			testIfProduct2StillExists(container1);
			testIfProduct2StillExists(container2);
			testIfProduct2StillExists(container3);
			
			try
			{
				pManager.getContainersForProduct(product2);
			}
			catch (NotFoundException e)
			{
				assertTrue(true);
				return;
			}
			catch(Exception e)
			{
				fail("Unexcepted exception");
			}
			fail("Product still associated with container");
		}
		catch (NotFoundException e)
		{
			fail("Something went wrong with product removal test");
		}
	}

	private void testIfProduct2StillExists(Container container) throws NotFoundException
	{
		Iterator<Product> prodIterator = pManager.getProductsForContainer(container);
		boolean productStillExists = false;
		
		while(prodIterator.hasNext())
		{
			Product p = prodIterator.next();
			if (p == product2)
				productStillExists = true;
		}
		assertFalse("Product in _containerToProductMap", productStillExists);
	}
	
	@Test
	public void testCanRemoveProduct() 
	{
		addThreeProducts();
		assertFalse(pManager.canRemoveProduct(null));
		assertFalse(pManager.canRemoveProduct(product4));
		assertTrue(pManager.canRemoveProduct(product1));
		pManager.removeProduct(product1);
		assertFalse(pManager.canRemoveProduct(product1));
	}

	@SuppressWarnings({ "rawtypes", "unchecked" })
	@Test
	public void testRemoveContainerFromAllProductsSingle() throws NotFoundException
	{
		addProductsAndContainers();
		
		pManager.removeContainersFromAllProducts(getContainersForProduct3()); //remove container2
		Set containerSet = new HashSet<Container>();
		Iterator contIter = pManager.getContainersForProduct(product1);
		testIteratorToSet(contIter, containerSet);
		
		contIter = pManager.getContainersForProduct(product3);
		testIteratorToSet(contIter, containerSet);
		
		containerSet.add(container1);
		containerSet.add(container3);
		contIter = pManager.getContainersForProduct(product2);
		testIteratorToSet(contIter, containerSet);
		testRemovalOfContainer(container2);
	}
	
	private void testRemovalOfContainer(Container container)
	{
		try
		{
			pManager.getProductsForContainer(container);
		}
		catch(NotFoundException e)
		{
			assertTrue("Container in _containerToProductMap", true);
			return;
		}
		catch(Exception e)
		{
			fail("unexcepted exception");
		}
		fail("Container still in _containerToProductMap");
	}
	
	@SuppressWarnings({ "rawtypes", "unchecked" })
	@Test
	public void testRemoveContainerFromAllProductsMultiple() throws NotFoundException
	{
		addProductsAndContainers();
		pManager.addProductToContainer(product3, container3);
		Set contSet = getContainersForProduct3();
		contSet.add(container3);
		
		pManager.removeContainersFromAllProducts(contSet); //containers 2, 3
		Set containerSet = new HashSet<Container>();
		Iterator contIter = pManager.getContainersForProduct(product1);
		testIteratorToSet(contIter, containerSet);
		
		contIter = pManager.getContainersForProduct(product3);
		testIteratorToSet(contIter, containerSet);
		
		containerSet.add(container1);
		contIter = pManager.getContainersForProduct(product2);
		testIteratorToSet(contIter, containerSet);
		
		testRemovalOfContainer(container2);
		testRemovalOfContainer(container3);
	}

	@SuppressWarnings({ "rawtypes", "unchecked" })
	@Test
	public void testRemoveProductFromContainer() throws IllegalAddException, NotFoundException 
	{
		addProductsAndContainers();
		Container container4 = rootSU.addContainer("Fourth");
		pManager.addProductToContainer(product2, container4);
		pManager.removeProductFromContainer(product2, container3);
		testIfProduct2StillExists(container3);
		
		Set set = getContainersForProduct2();
		set.add(container4);
		set.remove(container3);
		Iterator iterator = pManager.getContainersForProduct(product2);
		testIteratorToSet(iterator, set);
	}

	@Test
	public void testCanRemoveProductFromContainer() 
	{
		assertFalse(pManager.canRemoveProductFromContainer(product1, container1));
		
		addProductsAndContainers();
		
		assertFalse(pManager.canRemoveProductFromContainer(null, container2));
		assertFalse(pManager.canRemoveProductFromContainer(product2, null));
		
		assertFalse(pManager.canRemoveProductFromContainer(product3, container1));
		assertTrue(pManager.canRemoveProductFromContainer(product2, container1));
		
		try {
			iManager.createItem(product2, container1);
		} catch (IllegalAddException e) {
			fail("Unable to create item");
		}
		assertFalse(pManager.canRemoveProductFromContainer(product2, container1));
	}

	private void addThreeProducts()
	{
		pManager.addProduct(product1);
		pManager.addProduct(product2);
		pManager.addProduct(product3);
	}
	
	private void addProductsAndContainers()
	{
		addThreeProducts();
		pManager.addProductToContainer(product2, container1);
		pManager.addProductToContainer(product2, container2);
		pManager.addProductToContainer(product2, container3);
		pManager.addProductToContainer(product3, container2);
	}
	
	private Set<Container> getContainersForProduct1()
	{
		Set<Container> set = new HashSet<Container>();
		set.add(rootSU);
		return set;
	}
	
	private Set<Container> getContainersForProduct2()
	{
		Set<Container> set = new HashSet<Container>();
		set.add(container1);
		set.add(container2);
		set.add(container3);
		set.add(rootSU);
		return set;
	}
	
	private Set<Container> getContainersForProduct3()
	{
		Set<Container> set = new HashSet<Container>();
		set.add(container2);
		set.add(rootSU);
		return set;
	}
	
	private Set<Product> getProductsForContainer1()
	{
		Set<Product> set = new HashSet<Product>();
		set.add(product2);
		return set;
	}
	
	private Set<Product> getProductsForContainer2()
	{
		Set<Product> set = new HashSet<Product>();
		set.add(product2);
		set.add(product3);
		return set;
	}
}
